#include "glwindow.h"

#include "utils.h"

#include <iostream>
#include <sstream>

const float FPS_FILTER_COEFFICIENT = 0.1;

GLWindow::GLWindow() : fpsFiltered(0)
{
	Glib::RefPtr<Gdk::GL::Config> glconfig = Gdk::GL::Config::create(
		Gdk::GL::MODE_RGBA   |
		Gdk::GL::MODE_ALPHA  |
		Gdk::GL::MODE_DEPTH  |
		Gdk::GL::MODE_DOUBLE);
	if (!glconfig || !glconfig->has_alpha())
		error = "Unable to initialize OpenGL window";
	else
	{
		if (!set_gl_capability(glconfig))
			error = "Unable to initialize OpenGL window capability";
	}
}

void GLWindow::on_realize()
{
	Gtk::DrawingArea::on_realize();
	
	GLWindowContext context(get_gl_window(), get_gl_context());
	
	setupViewport();
	
	if (!renderer.initialize())
	{
		writeOutput("Renderer initialization failed");
		
		if (renderer.getRequiredGlVersion() != renderer.getSupportedGlVersion())
		{
			Gtk::MessageDialog dialog("Your card does not support "+renderer.getRequiredGlVersion());
			dialog.set_secondary_text("Your card only supports "+renderer.getSupportedGlVersion()+". This application may crash or some features may not operate properly. In general, an NVIDIA Geforce 8 series or AMD Radeon 3000 series or better is required.");

			dialog.run();
		}
	}
	
	fpsTimer.start();
}

bool GLWindow::on_configure_event(GdkEventConfigure* event)
{
	GLWindowContext context(get_gl_window(), get_gl_context());
	if (!context)
		return false;
	
	setupViewport();
	
	return true;
}

bool GLWindow::on_expose_event(GdkEventExpose* event)
{
	GLWindowContext context(get_gl_window(), get_gl_context());
	if (!context)
	{
		return false;
	}
	
	std::stringstream log;
	
	//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//log << "glClear\n";
	
	renderer.draw();
	
	if (context.glwindow->is_double_buffered())
		context.glwindow->swap_buffers();
	else
		glFlush();
	log << "glFlush\n";
	
	fpsFiltered = (1.0/fpsTimer.elapsed())*FPS_FILTER_COEFFICIENT+fpsFiltered*(1.0-FPS_FILTER_COEFFICIENT);
	fpsTimer.reset();
	
	//glLogClear();
	
	return true;
}

void GLWindow::reconnectIdle()
{
	if (!connectionIdle.connected())
	{
		connectionIdle = Glib::signal_idle().connect(sigc::mem_fun(*this, &GLWindow::on_idle), Glib::PRIORITY_DEFAULT_IDLE+1);
	}
}

bool GLWindow::on_map_event(GdkEventAny* event)
{
	reconnectIdle();
	
	return true;
}

bool GLWindow::on_unmap_event(GdkEventAny* event)
{
	if (connectionIdle.connected())
		connectionIdle.disconnect();
	
	return true;
}

bool GLWindow::on_visibility_notify_event(GdkEventVisibility* event)
{
	if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
	{
		if (connectionIdle.connected())
			connectionIdle.disconnect();
	}
	else
	{
		reconnectIdle();
	}
	
	return true;
}

bool GLWindow::on_idle()
{
	// Invalidate the whole window.
	get_window()->invalidate_rect(get_allocation(), false);

	// Update window synchronously (fast).
	get_window()->process_updates(false);

	return true;
}

void GLWindow::writeOutput(std::string text)
{
	if (!outputWindow.empty())
		outputWindow(text);
	else
		std::cerr << text << std::endl;
}

void GLWindow::setupViewport()
{
	glViewport(0, 0, get_width(), get_height());
	
	if (sceneRoot)
		renderer.updateConfiguration(sceneRoot.get(), get_width(), get_height());
	
	std::stringstream info;
	info << "Viewport changed to " << get_width() << "x" << get_height();
	writeOutput(info.str());
}

void GLWindow::sendSceneToRenderer(const SceneNode & root, bool fast)
{
	sceneRoot = root;
	renderer.updateConfiguration(root, get_width(), get_height(), fast);
}
